/*************************************************************************
 *
 *  Copyright (C) 2013 Florian Dang <florian.coin@gmail.com>
 *
 *  This file is part of NatusVerse.
 *
 *  NatusVerse is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  NatusVerse is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NatusVerse.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/

#include "raytracer/raytrace.h"

#include <stdio.h>

#ifdef INTEL_TBB
# include <tbb/task_scheduler_init.h>
# include <tbb/parallel_for.h>
# include <tbb/blocked_range.h>
#else
# ifdef _OPENMP
#  include <omp.h>
# endif
#endif

#include "raytracer/objects/point.h"
#include "raytracer/scene.h"
#include "graphics/color.h"
#include "perfs.h"

namespace raytracer
{
typedef std::vector<Object *> VecObject; // pointer here
typedef std::vector<Light>    VecLight;

void Raytrace::Raytracing()
{
	Scene scene = Scene::TestScene1(win().GetW(), win().GetH());

	win().InitGraphics(); // Graphical window

#if 1
	float sign = 1.0f;
	float sign2 = -1.0f;
	Light TmpLight;
	
	// Light remove/add animation
	for(int i = 0; i < 3; i ++)
	{
		TmpLight = scene.vec_light()[0];
		RaytraceScene(scene);
		scene.RemoveLight(0);
		RaytraceScene(scene);
		scene.AddLight(TmpLight);
	}

	//scene.RemoveLight(1);

	// Light move animation
	for(int i = 0; i < 60; i ++)
	{
		scene.MoveLightX(0, -400.0f, 200.0f, sign);
		scene.MoveLightX(1, -300.0f, 300.0f, sign2);
		RaytraceScene(scene);
		scene.MoveObject(0, -0.4f, -0.2f, 0.2f);
		scene.MoveObject(1, 0.1f, 0.1f, -0.06f);
	}
#else
		RaytraceScene(scene);
#endif

	win().PrintScreen();
	win_.WaitEscape();
}

void Raytrace::RaytraceScene(Scene scene)
{
	set_scene(scene);
	//Raytrace::scene().Print();

	double start = perfs::my_gettimeofday();

	#pragma omp parallel for schedule(dynamic, 2)
	for (int i = 0; i < w(); i ++)
	{
		for (int j = 0; j < h(); j ++)
		{
			win().AddPix(i, j, ComputePixel(i ,j).color());
			//vec_color_[i * w() + j] = ComputePixel(i ,j).color();
		} // end for j
		// win().PrintScreen();  // Fancy drawing
	} // end for i

	double end = perfs::my_gettimeofday();

	printf(" (£) [Render] %f s\n", end - start);
	//printf(".");
	win().PrintScreen();
}

//see http://www.cl.cam.ac.uk/teaching/2003/AdvGraph/raytracing.html
color::Color Raytrace::ComputePixel(int x, int y)
{
	color::Color pixel_color(color::black);

	Vector dir_view;
	// viewing ray
	dir_view = view_dir() + 0.5 * (2 * y + 1 - h()) * yinc() + 0.5 * (2 * x + 1 - w()) * xinc();
	dir_view.Normalize();

	Ray ray_view_reflect(eye(), dir_view); // optimized eye() access

	float coeff_recurs_reflect = 1.0f;
	int level = 0;

	//	Object* nearest_object = Raytrace::scene().vec_object()[0];
	Object* nearest_object = NULL; // waaa.. nice optimization...... -_-

	// REFLECTION
	// avoid recursive functions for stack memory issues and further parallel computing
	while((coeff_recurs_reflect > Object::kSmall) && (level < 4))
	{
		float nearest_t = Object::kInfinite;

		scene().NearestObject(ray_view_reflect, nearest_object, nearest_t);

		// if object exists and reflection possible
		if(nearest_t < Object::kInfinite)
		{
			coeff_recurs_reflect *= nearest_object->material().coeff_reflect();
			Point intersection = ray_view_reflect.start() + nearest_t * ray_view_reflect.direction();
			Vector vec_normal = nearest_object->Normal(intersection);
			// for each light
			for (int i = 0; i < numlights(); i ++) // optimized num_lights() access
			// for (VecLight::iterator current_light = scene().vec_light().begin();
			// 	 current_light != scene().vec_light().end();
			// 	 ++ current_light)
			// using iterator is dangerous because of pointer access
			{
				Light current_light = scene().vec_light()[i];
				Vector vec_dir_light = current_light.source() - intersection;

				// float dist = sqrtf(vec_dir_light.SquareNorm()); // for attenuation distance not necessary

				// Shadow rays
				if (vec_normal * vec_dir_light <= 0.0f)
					continue;

				vec_dir_light.Normalize();
				// ray is launched a little further from the surface
				Ray ray_light = Ray(intersection + Object::kSmallShadow * vec_dir_light, vec_dir_light);

				if(Raytrace::scene().IsInShadow(ray_light) == false)
				{
					// adding diffuse + specular coeff
					float coeff_brdf = nearest_object->material().BrdfLambert(ray_light, vec_normal, coeff_recurs_reflect);
					coeff_brdf += nearest_object->material().BrdfBlinnPhong(ray_light, vec_normal, coeff_recurs_reflect, ray_view_reflect);
					// coeff_brdf /= dist * 0.0028f;  // for attenuation distance not necessary
					nearest_object->material().BrdfReflect(current_light, coeff_brdf, pixel_color);
				}
			} // end for light

			// REFLECTION ray...
			float costhetai = (ray_view_reflect.direction() * vec_normal);
			dir_view = (ray_view_reflect.direction() - ((2.0f * costhetai) * vec_normal));
			// ray is launched a little further from the surface
			ray_view_reflect.set_start(intersection + Object::kSmallReflect * dir_view);
			ray_view_reflect.set_direction(dir_view.Normalize());
		} // if object does not exist
		else
		{
			// no need to compute reflection for next iteration
			// important for optimization
			coeff_recurs_reflect = 0.0f;
		} // end if object exists

		pixel_color.CalibrateMin();
		pixel_color.SetColor();

		level ++;
	} // end while reflection

	//float coeff_recurs_refract = 1.0f;

	return pixel_color;
}
} // end namespace
